package rstplus;

/*
 * RST - Russian SMS transliterator
 * Copyleft 2011
 * Author: Alexey Shat'ko (Алексей Шатько), rstapp@yandex.ru
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this source code (in file copying.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


import java.util.*;
import javax.microedition.rms.*;
import java.io.*;

public final class HistoryStorage {

    private static boolean enabled = false;

    public static void setEnabled(boolean _enabled) {
        if (!_enabled) {
            unload();
            if (rsHeaders != null) {
                try {
                    rsHeadersOpen = 0;
                    rsHeaders.closeRecordStore();
                    rsHeaders = null;
                } catch (Exception exc) {
                    //Skip
                }
            }
            try {
                RecordStore.deleteRecordStore(RS_HEADERS_NAME);
            } catch (Exception exc) {
                //Skip
            }
            if (rsTexts != null) {
                try {
                    rsTextsOpen = 0;
                    rsTexts.closeRecordStore();
                    rsTexts = null;
                } catch (Exception exc) {
                    //Skip
                }
            }
            try {
                RecordStore.deleteRecordStore(RS_TEXTS_NAME);
            } catch (Exception exc) {
                //Skip
            }
        }
        enabled = _enabled;
    }

    public static boolean isEnabled() {
        return enabled;
    }
    private static final String RS_HEADERS_NAME = "rstplus.hheaders";
    private static RecordStore rsHeaders = null;
    private static int rsHeadersOpen = 0;

    private static void rsHeadersOpen() throws RecordStoreException {
        if (rsHeadersOpen <= 0) {
            rsHeaders = RecordStore.openRecordStore(RS_HEADERS_NAME, true);
            rsHeadersOpen = 1;
        } else {
            rsHeadersOpen++;
        }
    }

    private static void rsHeadersClose() throws RecordStoreException {
        if (rsHeadersOpen > 1) {
            rsHeadersOpen--;
        } else if (rsHeadersOpen == 1) {
            rsHeaders.closeRecordStore();
            rsHeaders = null;
            rsHeadersOpen = 0;
        }
    }
    private static final String RS_TEXTS_NAME = "rstplus.htexts";
    private static RecordStore rsTexts = null;
    private static int rsTextsOpen = 0;

    private static void rsTextsOpen() throws RecordStoreException {
        if (rsTextsOpen <= 0) {
            rsTexts = RecordStore.openRecordStore(RS_TEXTS_NAME, true);
            rsTextsOpen = 1;
        } else {
            rsTextsOpen++;
        }
    }

    private static void rsTextsClose() throws RecordStoreException {
        if (rsTextsOpen > 1) {
            rsTextsOpen--;
        } else if (rsTextsOpen == 1) {
            rsTexts.closeRecordStore();
            rsTexts = null;
            rsTextsOpen = 0;
        }
    }
    private static Vector items = null;

    public static void load() throws RecordStoreException {
        if (!enabled) {
            throw new RuntimeException();
        }
        items = new Vector();
        rsHeadersOpen();
        try {
            RecordEnumeration re = rsHeaders.enumerateRecords(null, null, false);
            try {
                while (re.hasNextElement()) {
                    int id = re.nextRecordId();
                    byte[] record = rsHeaders.getRecord(id);
                    items.addElement(new HistoryItemHeader(id, record));
                }
            } finally {
                re.destroy();
            }
        } finally {
            rsHeadersClose();
        }
        sortItems();
    }

    public static void unload() {
        items = null;
    }

    public static int getItemsCount() {
        return items.size();
    }

    public static HistoryItemHeader getHeader(int index) {
        return (HistoryItemHeader) items.elementAt(index);
    }

    public static String getSentText(int index) throws RecordStoreException {
        HistoryItemHeader hdr = (HistoryItemHeader) items.elementAt(index);
        int id = hdr.getSentTextID();
        rsTextsOpen();
        try {
            byte[] record = rsTexts.getRecord(id);
            try {
                return byteArrayToStr(record);
            } catch (IOException exc) {
                throw new RuntimeException();
            }
        } finally {
            rsTextsClose();
        }
    }

    public static String getSourceText(int index) throws RecordStoreException {
        HistoryItemHeader hdr = (HistoryItemHeader) items.elementAt(index);
        int id = hdr.getSourceTextID();
        rsTextsOpen();
        try {
            byte[] record = rsTexts.getRecord(id);
            try {
                return byteArrayToStr(record);
            } catch (IOException exc) {
                throw new RuntimeException();
            }
        } finally {
            rsTextsClose();
        }
    }

    public static void delete(int index) throws RecordStoreException {
        HistoryItemHeader hdr = (HistoryItemHeader) items.elementAt(index);
        items.removeElementAt(index);
        rsHeadersOpen();
        try {
            rsHeaders.deleteRecord(hdr.getHeaderId());
        } finally {
            rsHeadersClose();
        }
        rsTextsOpen();
        try {
            rsTexts.deleteRecord(hdr.getSourceTextID());
            if (hdr.getSentTextID() != hdr.getSourceTextID()) {
                rsTexts.deleteRecord(hdr.getSentTextID());
            }
        } finally {
            rsTextsClose();
        }
        load();
    }

    public static void clear() throws RecordStoreException {
        if (items != null) {
            items.removeAllElements();
        }
        if ((rsTextsOpen > 0) && (rsTexts != null)) {
            rsTexts.closeRecordStore();
        }
        if ((rsHeadersOpen > 0) && (rsHeaders != null)) {
            rsHeaders.closeRecordStore();
        }
        try {
            RecordStore.deleteRecordStore(RS_HEADERS_NAME);
        } catch (RecordStoreNotFoundException exc) {
            //skip
        }
        try {
            RecordStore.deleteRecordStore(RS_TEXTS_NAME);
        } catch (RecordStoreNotFoundException exc) {
            //skip
        }
        if (rsHeadersOpen > 0) {
            rsHeaders = RecordStore.openRecordStore(RS_HEADERS_NAME, true);
        }
        if (rsTextsOpen > 0) {
            rsTexts = RecordStore.openRecordStore(RS_TEXTS_NAME, true);
        }
    }

    public static void add(String sent, String source, String number) throws RecordStoreException {
        if (!enabled) {
            return;
        }
        rsTextsOpen();
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                DataOutputStream dos = new DataOutputStream(baos);
                try {
                    dos.writeUTF(number);
                    byte[] dateRecord = (new SerializableDate(new Date())).toByteArray();
                    dos.write(dateRecord);
                    byte[] sourceRecord = strToByteArray(source);
                    int sourceId = rsTexts.addRecord(sourceRecord, 0, sourceRecord.length);
                    dos.writeInt(sourceId);
                    if (source.equals(sent)) {
                        dos.writeInt(sourceId);
                    } else {
                        byte[] sentRecord = strToByteArray(sent);
                        int sentId = rsTexts.addRecord(sentRecord, 0, sentRecord.length);
                        dos.writeInt(sentId);
                    }
                } finally {
                    dos.close();
                }
            } catch (IOException exc) {
                throw new RuntimeException();
            }
            byte[] headerRecord = baos.toByteArray();
            rsHeadersOpen();
            try {
                rsHeaders.addRecord(headerRecord, 0, headerRecord.length);
                trim();
            } finally {
                rsHeadersClose();
            }
        } finally {
            rsTextsClose();
        }
    }

    //Should not be called is items!=null
    public static void trim() throws RecordStoreException {
        if (items != null) {
            throw new RuntimeException();
        }
        rsHeadersOpen();
        try {
            int n = rsHeaders.getNumRecords();
            int max = SettingsManager.getHistorySize();
            if (n > max) {
                rsTextsOpen();
                try {
                    Vector ids = new Vector();
                    RecordEnumeration re = rsHeaders.enumerateRecords(null, null, false);
                    try {
                        while (re.hasNextElement()) {
                            ids.addElement(new Integer(re.nextRecordId()));
                        }
                    } finally {
                        re.destroy();
                    }
                    if (ids.size() != n) {
                        throw new RuntimeException();
                    }
                    //Sort
                    for (int i = 0; i < n - 1; i++) {
                        int k = i;
                        int ii = ((Integer) ids.elementAt(i)).intValue();
                        int ki = ii;
                        for (int j = i + 1; j < n; j++) {
                            int ji = ((Integer) ids.elementAt(j)).intValue();
                            if (ji < ki) {
                                k = j;
                                ki = ji;
                            }
                        }
                        if (k != i) {
                            Object t = ids.elementAt(i);
                            ids.setElementAt(ids.elementAt(k), i);
                            ids.setElementAt(t, k);
                        }
                    }
                    for (int i = 0; i < n - max; i++) {
                        int id = ((Integer) ids.elementAt(i)).intValue();
                        HistoryItemHeader hdr = new HistoryItemHeader(id, rsHeaders.getRecord(id));
                        rsTexts.deleteRecord(hdr.getSentTextID());
                        if (hdr.getSentTextID() != hdr.getSourceTextID()) {
                            rsTexts.deleteRecord(hdr.getSourceTextID());
                        }
                        rsHeaders.deleteRecord(id);
                    }
                } finally {
                    rsTextsClose();
                }
            }
        } finally {
            rsHeadersClose();
        }
    }

    private static byte[] strToByteArray(String text) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        try {
            dos.writeUTF(text);
        } finally {
            dos.close();
        }
        return baos.toByteArray();
    }

    private static String byteArrayToStr(byte[] data) throws IOException {
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
        try {
            return dis.readUTF();
        } finally {
            dis.close();
        }
    }

    private static void sortItems() {
        int len = items.size();
        for (int i = 0; i < len - 1; i++) {
            int k = i;
            HistoryItemHeader ii = (HistoryItemHeader) items.elementAt(i);
            HistoryItemHeader ki = ii;
            int kid = ki.getHeaderId();
            for (int j = i + 1; j < len; j++) {
                HistoryItemHeader ji = (HistoryItemHeader) items.elementAt(j);
                int jid = ji.getHeaderId();
                if (jid > kid) {
                    kid = jid;
                    ki = ji;
                    k = j;
                }
            }
            if (i != k) {
                items.setElementAt(ki, i);
                items.setElementAt(ii, k);
            }
        }
    }

    public static final class HistoryItemHeader {

        private int headerId;
        private String number;
        private SerializableDate date;
        private int sourceTextID;
        private int sentTextID;

        public HistoryItemHeader(int headerId, String number,
                SerializableDate date, int sourceTextID, int sentTextID) {
            this.headerId = headerId;
            this.number = number;
            this.date = date;
            this.sourceTextID = sourceTextID;
            this.sentTextID = sentTextID;
        }

        public HistoryItemHeader(int id, byte[] record) {
            headerId = id;
            try {
                DataInputStream dis = new DataInputStream(new ByteArrayInputStream(record));
                try {
                    number = dis.readUTF();
                    byte[] dateRecord = new byte[SerializableDate.SERIALIZED_SIZE];
                    dis.read(dateRecord, 0, dateRecord.length);
                    date = new SerializableDate(dateRecord);
                    sourceTextID = dis.readInt();
                    sentTextID = dis.readInt();
                } finally {
                    dis.close();
                }
            } catch (IOException exc) {
                throw new RuntimeException();
            }
        }

        public SerializableDate getDate() {
            return date;
        }

        public int getHeaderId() {
            return headerId;
        }

        public String getNumber() {
            return number;
        }

        public int getSentTextID() {
            return sentTextID;
        }

        public int getSourceTextID() {
            return sourceTextID;
        }

        public boolean isSentSource() {
            return (sentTextID == sourceTextID);
        }
    }
}
